package cn.exrick.xboot.common.redis;

import cn.exrick.xboot.config.cache.RedisObjectSerializer;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.connection.RedisClusterNode;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * Redis Repository redis 基本操作 可扩展,基本够用了
 *
 * @author pocky
 * @date 2019-08-06 10:42
 */
@Slf4j
public class RedisRepositoryImpl implements CacheRepository {

  /**
   * 默认编码
   */
  private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
  /**
   * value 序列化
   */
  private static final RedisObjectSerializer OBJECT_SERIALIZER = new RedisObjectSerializer();

  /**
   * Spring Redis Template
   */
  private RedisTemplate<String, Object> redisTemplate;

  public RedisRepositoryImpl(RedisTemplate<String, Object> redisTemplate) {
    this.redisTemplate = redisTemplate;
  }

  /**
   * 获取链接工厂
   */
  public RedisConnectionFactory getConnectionFactory() {
    return this.redisTemplate.getConnectionFactory();
  }

  /**
   * 获取 RedisTemplate对象
   */
  public RedisTemplate<String, Object> getRedisTemplate() {
    return redisTemplate;
  }

  /**
   * 清空DB
   *
   * @param node redis 节点
   */
  public void flushDb(RedisClusterNode node) {
    this.redisTemplate.opsForCluster().flushDb(node);
  }


  /**
   * 添加到带有 过期时间的  缓存
   *
   * @param key redis主键
   * @param value 值
   * @param time 过期时间(单位秒)
   */
  public void setExpire(final byte[] key, final byte[] value, final long time) {
    redisTemplate.execute((RedisCallback<Long>) connection -> {
      connection.setEx(key, time, value);
      log.debug("[redisTemplate redis]放入 缓存  url:{} ========缓存时间为{}秒", key, time);
      return 1L;
    });
  }

  /**
   * 添加到带有 过期时间的  缓存
   *
   * @param key redis主键
   * @param value 值
   * @param time 过期时间(单位秒)
   */
  @Override
  public void setExpire(final String key, final Object value, final long time) {
    redisTemplate.execute((RedisCallback<Long>) connection -> {
      RedisSerializer<String> serializer = getRedisSerializer();
      byte[] keys = serializer.serialize(key);
      byte[] values = OBJECT_SERIALIZER.serialize(value);
      connection.setEx(keys, time, values);
      return 1L;
    });
  }

  /**
   * 一次性添加数组到   过期时间的  缓存，不用多次连接，节省开销
   *
   * @param keys redis主键数组
   * @param values 值数组
   * @param time 过期时间(单位秒)
   */
  public void setExpire(final String[] keys, final Object[] values, final long time) {
    redisTemplate.execute((RedisCallback<Long>) connection -> {
      RedisSerializer<String> serializer = getRedisSerializer();
      for (int i = 0; i < keys.length; i++) {
        byte[] bKeys = serializer.serialize(keys[i]);
        byte[] bValues = OBJECT_SERIALIZER.serialize(values[i]);
        connection.setEx(bKeys, time, bValues);
      }
      return 1L;
    });
  }


  /**
   * 一次性添加数组到   过期时间的  缓存，不用多次连接，节省开销
   *
   * @param keys the keys
   * @param values the values
   */
  public void set(final String[] keys, final Object[] values) {
    redisTemplate.execute((RedisCallback<Long>) connection -> {
      RedisSerializer<String> serializer = getRedisSerializer();
      for (int i = 0; i < keys.length; i++) {
        byte[] bKeys = serializer.serialize(keys[i]);
        byte[] bValues = OBJECT_SERIALIZER.serialize(values[i]);
        connection.set(bKeys, bValues);
      }
      return 1L;
    });
  }


  /**
   * 添加到缓存
   *
   * @param key the key
   * @param value the value
   */
  @Override
  public void set(final String key, final Object value) {
    redisTemplate.execute((RedisCallback<Long>) connection -> {
      RedisSerializer<String> serializer = getRedisSerializer();
      byte[] keys = serializer.serialize(key);
      byte[] values = OBJECT_SERIALIZER.serialize(value);
      connection.set(keys, values);
      log.debug("[redisTemplate redis]放入 缓存  url:{}", key);
      return 1L;
    });
  }

  /**
   * @author: guojia
   * @description: value 不存在设置缓存，存在不改变,如用于自增的 value 必须为 Inetger
   * @date: 2022/3/10
   */
  public Boolean stringSetIfAbsent(String key, Object value, long timeout, TimeUnit unit){
    return redisTemplate.opsForValue().setIfAbsent(key,value,timeout,unit);
  }

  /**
   * 查询在这个时间段内即将过期的key 注意： 在服务器上执行 keys 命令是非常耗时的， 在使用该方法前，请三思!!! 在服务器上执行 keys ** 命令是非常恐怖的，禁止执行!!!
   *
   * @param key the key
   * @param time the time
   * @return the list
   */
  public List<String> willExpire(final String key, final long time) {
    if (StrUtil.isEmpty(key)) {
      return Collections.emptyList();
    }
    if ("*".equals(key.trim())) {
      throw new IllegalArgumentException("禁止模糊查询所有的key");
    }
    final List<String> keysList = new ArrayList<>();
    redisTemplate.execute((RedisCallback<List<String>>) connection -> {
      Set<String> keys = redisTemplate.keys(key + "*");
      for (String key1 : keys) {
        Long ttl = connection.ttl(key1.getBytes(DEFAULT_CHARSET));
        if (0 <= ttl && ttl <= 2 * time) {
          keysList.add(key1);
        }
      }
      return keysList;
    });
    return keysList;
  }


  /**
   * 查询在以keyPatten的所有  key 注意： 在服务器上执行 keys 命令是非常耗时的， 在使用该方法前，请三思!!! 在服务器上执行 keys **
   * 命令是非常恐怖的，禁止执行!!!
   *
   * @param keyPatten the key patten
   * @return the set
   */
  public Set<String> keys(final String keyPatten) {
    if (StrUtil.isEmpty(keyPatten)) {
      return Collections.emptySet();
    }
    if ("*".equals(keyPatten.trim())) {
      throw new IllegalArgumentException("禁止模糊查询所有的key");
    }
    return redisTemplate
        .execute((RedisCallback<Set<String>>) connection -> redisTemplate.keys(keyPatten + "*"));
  }

  /**
   * 根据key获取对象
   *
   * @param key the key
   * @return the byte [ ]
   */
  public byte[] get(final byte[] key) {
    byte[] result = redisTemplate
        .execute((RedisCallback<byte[]>) connection -> connection.get(key));
    log.debug("[redisTemplate redis]取出 缓存  url:{} ", key);
    return result;
  }

  /**
   * 根据key获取对象
   *
   * @param key the key
   * @return the string
   */
  @Override
  public <T> T get(final String key) {
    T resultStr = redisTemplate.execute((RedisCallback<T>) connection -> {
      RedisSerializer<String> serializer = getRedisSerializer();
      byte[] keys = serializer.serialize(key);
      byte[] values = connection.get(keys);
      return (T) OBJECT_SERIALIZER.deserialize(values);
    });
    log.debug("[redisTemplate redis]取出 缓存  url:{} ", key);
    return resultStr;
  }

  @Override
  public <T> T getOrDef(String key, Function<String, ? extends T> function) {
    T resultStr = get(key);
    if (resultStr == null) {
      T value = function.apply(key);
      if (value != null) {
        set(key, value);
      }
      return value;
    }
    return resultStr;
  }

  /**
   * 根据key获取对象
   *
   * @param keyPatten the key patten
   * @return the keys values
   */
  public Map<String, Object> getKeysValues(final String keyPatten) {
    log.debug("[redisTemplate redis]  getValues()  patten={} ", keyPatten);
    return redisTemplate.execute((RedisCallback<Map<String, Object>>) connection -> {
      RedisSerializer<String> serializer = getRedisSerializer();
      Map<String, Object> maps = new HashMap<>(16);
      Set<String> keys = redisTemplate.keys(keyPatten + "*");
      if (CollectionUtil.isNotEmpty(keys)) {
        for (String key : keys) {
          byte[] bKeys = serializer.serialize(key);
          byte[] bValues = connection.get(bKeys);
          Object value = OBJECT_SERIALIZER.deserialize(bValues);
          maps.put(key, value);
        }
      }
      return maps;
    });
  }

  /**
   * Ops for hash hash operations.
   *
   * @return the hash operations
   */
  public <T> HashOperations<String, String, T> opsForHash() {
    return redisTemplate.opsForHash();
  }

  /**=======================对HashMap操作=======================*/
  /**
   * 对HashMap操作
   *
   * @param key the key
   * @param hashKey the hash key
   * @param hashValue the hash value
   */
  public void putHashValue(String key, String hashKey, Object hashValue) {
    log.debug("[redisTemplate redis]  putHashValue()  key={},hashKey={},hashValue={} ", key,
        hashKey, hashValue);
    opsForHash().put(key, hashKey, hashValue);
  }

  /**
   * 获取单个field对应的值
   *
   * @param key the key
   * @param hashKey the hash key
   * @return the hash values
   */
  public <T> T getHashValues(String key, String hashKey) {
    log.debug("[redisTemplate redis]  getHashValues()  key={},hashKey={}", key, hashKey);
    return (T) opsForHash().get(key, hashKey);
  }

  /**
   * 根据key值删除
   *
   * @param key the key
   * @param hashKeys the hash keys
   */
  public void delHashValues(String key, Object... hashKeys) {
    log.debug("[redisTemplate redis]  delHashValues()  key={}", key);
    opsForHash().delete(key, hashKeys);
  }

  /**
   * key只匹配map
   *
   * @param key the key
   * @return the hash value
   */
  public Map<String, Object> getHashValue(String key) {
    log.debug("[redisTemplate redis]  getHashValue()  key={}", key);
    return opsForHash().entries(key);
  }

  /**
   * 批量添加
   *
   * @param key the key
   * @param map the map
   */
  public void putHashValues(String key, Map<String, Object> map) {
    opsForHash().putAll(key, map);
  }

  /**
   * 集合数量
   *
   * @return the long
   */
  public long dbSize() {
    return redisTemplate.execute(RedisServerCommands::dbSize);
  }

  /**
   * 清空redis存储的数据
   *
   * @return the string
   */
  @Override
  public void flushDb() {
    redisTemplate.execute((RedisCallback<String>) connection -> {
      connection.flushDb();
      return "ok";
    });
  }

  /**
   * 判断某个主键是否存在
   *
   * @param key the key
   * @return the boolean
   */
  @Override
  public boolean exists(final String key) {
    return redisTemplate.execute(
        (RedisCallback<Boolean>) connection -> connection.exists(key.getBytes(DEFAULT_CHARSET)));
  }


  /**
   * 删除key
   *
   * @param keys the keys
   * @return the long
   */
  @Override
  public long del(final String... keys) {
    return redisTemplate.execute((RedisCallback<Long>) connection -> {
      long result = 0;
      for (String key : keys) {
        result += connection.del(key.getBytes(DEFAULT_CHARSET));
      }
      return result;
    });
  }

  /**
   * 获取 RedisSerializer
   *
   * @return the redis serializer
   */
  protected RedisSerializer<String> getRedisSerializer() {
    return redisTemplate.getStringSerializer();
  }

  /**
   * 对某个主键对应的值加一,value值必须是全数字的字符串
   *
   * @param key the key
   * @return the long
   */
  public long incr(final String key, long seconds) {
    return redisTemplate.execute((RedisCallback<Long>) connection -> {
      RedisSerializer<String> redisSerializer = getRedisSerializer();
      long val = connection.incr(redisSerializer.serialize(key));
      connection.expire(redisSerializer.serialize(key), seconds);
      return val;
    });
  }

  /**=======================对List操作=======================*/
  /**
   * redis List 引擎
   *
   * @return the list operations
   */
  public ListOperations<String, Object> opsForList() {
    return redisTemplate.opsForList();
  }

  /**
   * redis List数据结构 : 将一个或多个值 value 插入到列表 key 的表头
   *
   * @param key the key
   * @param value the value
   * @return the long
   */
  public Long leftPush(String key, Object value) {
    return opsForList().leftPush(key, value);
  }

  /**
   * redis List数据结构 : 移除并返回列表 key 的头元素
   *
   * @param key the key
   * @return the string
   */
  public Object leftPop(String key) {
    return opsForList().leftPop(key);
  }

  /**
   * redis List数据结构 :将一个或多个值 value 插入到列表 key 的表尾(最右边)。
   *
   * @param key the key
   * @param value the value
   * @return the long
   */
  public Long rightPush(String key, Object value) {
    return opsForList().rightPush(key, value);
  }

  /**
   * redis List数据结构 : 移除并返回列表 key 的末尾元素
   *
   * @param key the key
   * @return the string
   */
  public Object rightPop(String key) {
    return opsForList().rightPop(key);
  }


  /**
   * redis List数据结构 : 返回列表 key 的长度 ; 如果 key 不存在，则 key 被解释为一个空列表，返回 0 ; 如果 key 不是列表类型，返回一个错误。
   *
   * @param key the key
   * @return the long
   */
  public Long length(String key) {
    return opsForList().size(key);
  }


  /**
   * redis List数据结构 : 根据参数 i 的值，移除列表中与参数 value 相等的元素
   *
   * @param key the key
   * @param i the
   * @param value the value
   */
  public void remove(String key, long i, Object value) {
    opsForList().remove(key, i, value);
  }

  /**
   * redis List数据结构 : 将列表 key 下标为 index 的元素的值设置为 value
   *
   * @param key the key
   * @param index the index
   * @param value the value
   */
  public void set(String key, long index, Object value) {
    opsForList().set(key, index, value);
  }

  /**
   * redis List数据结构 : 返回列表 key 中指定区间内的元素，区间以偏移量 [start 和 end] 指定。
   *
   * @param key the key
   * @param start list的开始位置 （包含）
   * @param end list的结束位置 （包含）
   * @return the list
   */
  public <T> List<T> getList(String key, int start, int end) {
    return (List<T>) opsForList().range(key, start, end);
  }

  /**
   * redis List数据结构 : 批量存储
   *
   * @param key the key
   * @param list the list
   * @return the long
   */
  public <V> Long leftPushAll(String key, List<V> list) {
    return opsForList().leftPushAll(key, list);
  }

  /**
   * redis List数据结构 : 将值 value 插入到列表 key 当中，位于值 index 之前或之后,默认之后。
   *
   * @param key the key
   * @param index the index
   * @param value the value
   */
  public void insert(String key, long index, Object value) {
    opsForList().set(key, index, value);
  }

  /**=======================对set操作=======================*/
  /**
   * redis set 引擎
   *
   * @return the list operations
   */
  public SetOperations<String, Object> opsForSet() {
    return redisTemplate.opsForSet();
  }
  /**
   * 向集合添加一个或多个成员
   * @return Long
   */
  public Long addSetValue(String key, Object... value) {
    return opsForSet().add(key,value);
  }

  /**
   * 移除集合中一个或多个成员
   * @return Long
   */
  public Long removeSetValue(String key, Object... value) {
    return opsForSet().remove(key,value);
  }

  /**
   * 返回集合中的所有成员
   * @return Set<Object>
   */
  public <T> Set<T> getSetAllValues(String key) {
    return (Set<T>)opsForSet().members(key);
  }
  /**
   * 获取集合的成员数
   * @return Long
   */
  public Long getSetSize(String key) {
    return opsForSet().size(key);
  }

  /**
   * 随即获取值
   * @return Long
   */
  public <V> List<V> randomMembersSetValue(String key,long count) {
    return (List<V>) opsForSet().randomMembers(key,count);
  }

  /**
   * 搜索关键词保存热度
   * @param key
   * @param searchKey 搜索词
   * @return
   */
  public int incrementScore(String key, String searchKey){
    Long now = System.currentTimeMillis();
    ZSetOperations zSetOperations = this.redisTemplate.opsForZSet();
    ValueOperations<String, Object> valueOperations = this.redisTemplate.opsForValue();
    zSetOperations.incrementScore(key, searchKey, 1);
    valueOperations.getAndSet(key+ StrPool.COLON+searchKey, String.valueOf(now));
    return 1;
  }

  /**
   * 获取搜索关键词热榜
   * @param key
   * @param searchKey
   * @param size
   * @return
   */
  public List<String> getHotList(String key,String searchKey,int size) {
    Long now = System.currentTimeMillis();
    List<String> result = new ArrayList<>();
    ZSetOperations zSetOperations = this.redisTemplate.opsForZSet();
    ValueOperations<String, Object> valueOperations = this.redisTemplate.opsForValue();
    Set<String> value = zSetOperations.reverseRangeByScore(key, 0, Double.MAX_VALUE);
    //searchKey不为空的时候 推荐相关的最热前十名
    if(StringUtils.isNotEmpty(searchKey)){
      for (String val : value) {
        if (StringUtils.containsIgnoreCase(val, searchKey)) {
          if (result.size() >= size) {
            //只返回最热的前几名
            break;
          }
          Long time = Long.valueOf(valueOperations.get(key+StrPool.COLON+val).toString());
          if ((now - time) < 2592000000L) {
            //返回最近一个月的数据
            result.add(val);
          } else {
            //时间超过一个月没搜索就把这个词热度归0
            zSetOperations.add(key, val, 0);
          }
        }
      }
    }else{
      for (String val : value) {
        if (result.size() >= size) {
          //只返回最热的前几名
          break;
        }
        Long time = Long.valueOf(valueOperations.get(key+StrPool.COLON+val).toString());
        if ((now - time) < 2592000000L) {
          //返回最近一个月的数据
          result.add(val);
        } else {
          //时间超过一个月没搜索就把这个词热度归0
          zSetOperations.add(key, val, 0);
        }
      }
    }
    return result;
  }

}

